/*
 *  Copyright 2005 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.mybatis.generator.internal;

import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;
import static org.mybatis.generator.internal.util.messages.Messages.getString;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.mybatis.generator.api.CommentGenerator;
import org.mybatis.generator.api.FullyQualifiedTable;
import org.mybatis.generator.api.IntrospectedColumn;
import org.mybatis.generator.api.IntrospectedTable;
import org.mybatis.generator.api.JavaFormatter;
import org.mybatis.generator.api.JavaTypeResolver;
import org.mybatis.generator.api.Plugin;
import org.mybatis.generator.api.XmlFormatter;
import org.mybatis.generator.api.dom.DefaultJavaFormatter;
import org.mybatis.generator.api.dom.DefaultXmlFormatter;
import org.mybatis.generator.codegen.ibatis2.IntrospectedTableIbatis2Java2Impl;
import org.mybatis.generator.codegen.ibatis2.IntrospectedTableIbatis2Java5Impl;
import org.mybatis.generator.codegen.mybatis3.IntrospectedTableMyBatis3Impl;
import org.mybatis.generator.codegen.mybatis3.IntrospectedTableMyBatis3SimpleImpl;
import org.mybatis.generator.config.CommentGeneratorConfiguration;
import org.mybatis.generator.config.Context;
import org.mybatis.generator.config.JavaTypeResolverConfiguration;
import org.mybatis.generator.config.PluginConfiguration;
import org.mybatis.generator.config.PropertyRegistry;
import org.mybatis.generator.config.TableConfiguration;
import org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl;
import org.mybatis.generator.internal.util.StringUtility;

/**
 * This class creates the different objects needed by the generator
 * 
 * @author Jeff Butler
 */
public class ObjectFactory
{
    private static List<ClassLoader> externalClassLoaders;
    
    private static List<ClassLoader> resourceClassLoaders;
    static
    {
        externalClassLoaders = new ArrayList<ClassLoader>();
        resourceClassLoaders = new ArrayList<ClassLoader>();
    }
    
    /**
     * Utility class. No instances allowed
     */
    private ObjectFactory()
    {
        super();
    }
    
    /**
     * Adds a custom classloader to the collection of classloaders searched for resources. Currently, this is only used when searching for properties files that may be referenced in the configuration
     * file.
     * 
     * @param classLoader
     */
    public static synchronized void addResourceClassLoader(ClassLoader classLoader)
    {
        ObjectFactory.resourceClassLoaders.add(classLoader);
    }
    
    /**
     * Adds a custom classloader to the collection of classloaders searched for "external" classes. These are classes that do not depend on any of the generator's classes or interfaces. Examples are
     * JDBC drivers, root classes, root interfaces, etc.
     * 
     * @param classLoader
     */
    public static synchronized void addExternalClassLoader(ClassLoader classLoader)
    {
        ObjectFactory.externalClassLoaders.add(classLoader);
    }
    
    /**
     * This method returns a class loaded from the context classloader, or the classloader supplied by a client. This is appropriate for JDBC drivers, model root classes, etc. It is not appropriate
     * for any class that extends one of the supplied classes or interfaces.
     * 
     * @param type
     * @return the Class loaded from the external classloader
     * @throws ClassNotFoundException
     */
    public static Class<?> externalClassForName(String type)
        throws ClassNotFoundException
    {
        Class<?> clazz;
        for (ClassLoader classLoader : externalClassLoaders)
        {
            try
            {
                clazz = Class.forName(type, true, classLoader);
                return clazz;
            }
            catch (Throwable e)
            {
                // ignore - fail safe below
                ;
            }
        }
        return internalClassForName(type);
    }
    
    public static Object createExternalObject(String type)
    {
        Object answer;
        try
        {
            Class<?> clazz = externalClassForName(type);
            answer = clazz.newInstance();
        }
        catch (Exception e)
        {
            throw new RuntimeException(getString("RuntimeError.6", type), e); //$NON-NLS-1$
        }
        return answer;
    }
    
    public static Class<?> internalClassForName(String type)
        throws ClassNotFoundException
    {
        Class<?> clazz = null;
        try
        {
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            clazz = Class.forName(type, true, cl);
        }
        catch (Exception e)
        {
            // ignore - failsafe below
        }
        if (clazz == null)
        {
            clazz = Class.forName(type, true, ObjectFactory.class.getClassLoader());
        }
        return clazz;
    }
    
    public static URL getResource(String resource)
    {
        URL url;
        for (ClassLoader classLoader : resourceClassLoaders)
        {
            url = classLoader.getResource(resource);
            if (url != null)
            {
                return url;
            }
        }
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        url = cl.getResource(resource);
        if (url == null)
        {
            url = ObjectFactory.class.getClassLoader().getResource(resource);
        }
        return url;
    }
    
    public static Object createInternalObject(String type)
    {
        Object answer;
        try
        {
            Class<?> clazz = internalClassForName(type);
            answer = clazz.newInstance();
        }
        catch (Exception e)
        {
            throw new RuntimeException(getString("RuntimeError.6", type), e); //$NON-NLS-1$
        }
        return answer;
    }
    
    public static JavaTypeResolver createJavaTypeResolver(Context context, List<String> warnings)
    {
        JavaTypeResolverConfiguration config = context.getJavaTypeResolverConfiguration();
        String type;
        if (config != null && config.getConfigurationType() != null)
        {
            type = config.getConfigurationType();
            if ("DEFAULT".equalsIgnoreCase(type)) //$NON-NLS-1$
            {
                type = JavaTypeResolverDefaultImpl.class.getName();
            }
        }
        else
        {
            type = JavaTypeResolverDefaultImpl.class.getName();
        }
        JavaTypeResolver answer = (JavaTypeResolver)createInternalObject(type);
        answer.setWarnings(warnings);
        if (config != null)
        {
            answer.addConfigurationProperties(config.getProperties());
        }
        answer.setContext(context);
        return answer;
    }
    
    public static Plugin createPlugin(Context context, PluginConfiguration pluginConfiguration)
    {
        Plugin plugin = (Plugin)createInternalObject(pluginConfiguration.getConfigurationType());
        plugin.setContext(context);
        plugin.setProperties(pluginConfiguration.getProperties());
        return plugin;
    }
    
    public static CommentGenerator createCommentGenerator(Context context)
    {
        CommentGeneratorConfiguration config = context.getCommentGeneratorConfiguration();
        CommentGenerator answer;
        String type;
        if (config == null || config.getConfigurationType() == null)
        {
            type = DefaultCommentGenerator.class.getName();
        }
        else
        {
            type = config.getConfigurationType();
        }
        answer = (CommentGenerator)createInternalObject(type);
        if (config != null)
        {
            answer.addConfigurationProperties(config.getProperties());
        }
        return answer;
    }
    
    public static JavaFormatter createJavaFormatter(Context context)
    {
        String type = context.getProperty(PropertyRegistry.CONTEXT_JAVA_FORMATTER);
        if (!StringUtility.stringHasValue(type))
        {
            type = DefaultJavaFormatter.class.getName();
        }
        JavaFormatter answer = (JavaFormatter)createInternalObject(type);
        answer.setContext(context);
        return answer;
    }
    
    public static XmlFormatter createXmlFormatter(Context context)
    {
        String type = context.getProperty(PropertyRegistry.CONTEXT_XML_FORMATTER);
        if (!StringUtility.stringHasValue(type))
        {
            type = DefaultXmlFormatter.class.getName();
        }
        XmlFormatter answer = (XmlFormatter)createInternalObject(type);
        answer.setContext(context);
        return answer;
    }
    
    public static IntrospectedTable createIntrospectedTable(TableConfiguration tableConfiguration, FullyQualifiedTable table, Context context)
    {
        IntrospectedTable answer = createIntrospectedTableForValidation(context);
        answer.setFullyQualifiedTable(table);
        answer.setTableConfiguration(tableConfiguration);
        return answer;
    }
    
    /**
     * This method creates an introspected table implementation that is only usable for validation (i.e. for a context to determine if the target is ibatis2 or mybatis3).
     * 
     * @param context
     * @return
     */
    public static IntrospectedTable createIntrospectedTableForValidation(Context context)
    {
        String type = context.getTargetRuntime();
        if (!stringHasValue(type))
        {
            type = IntrospectedTableMyBatis3Impl.class.getName();
        }
        else if ("Ibatis2Java2".equalsIgnoreCase(type)) //$NON-NLS-1$
        {
            type = IntrospectedTableIbatis2Java2Impl.class.getName();
        }
        else if ("Ibatis2Java5".equalsIgnoreCase(type)) //$NON-NLS-1$
        {
            type = IntrospectedTableIbatis2Java5Impl.class.getName();
        }
        else if ("Ibatis3".equalsIgnoreCase(type)) //$NON-NLS-1$
        {
            type = IntrospectedTableMyBatis3Impl.class.getName();
        }
        else if ("MyBatis3".equalsIgnoreCase(type)) //$NON-NLS-1$
        {
            type = IntrospectedTableMyBatis3Impl.class.getName();
        }
        else if ("MyBatis3Simple".equalsIgnoreCase(type)) //$NON-NLS-1$
        {
            type = IntrospectedTableMyBatis3SimpleImpl.class.getName();
        }
        IntrospectedTable answer = (IntrospectedTable)createInternalObject(type);
        answer.setContext(context);
        return answer;
    }
    
    public static IntrospectedColumn createIntrospectedColumn(Context context)
    {
        String type = context.getIntrospectedColumnImpl();
        if (!stringHasValue(type))
        {
            type = IntrospectedColumn.class.getName();
        }
        IntrospectedColumn answer = (IntrospectedColumn)createInternalObject(type);
        answer.setContext(context);
        return answer;
    }
}
